// breaking out the inner methods generator in here
import { validateAsync } from 'jsonql-params-validator'
import {
  JsonqlValidationError,
  JsonqlError,
  clientErrorsHandler,
  finalCatch
} from 'jsonql-errors'
import { LOGOUT_NAME, ISSUER_NAME, KEY_WORD } from 'jsonql-constants'

/**
 * generate authorisation specific methods
 * @param {object} jsonqlInstance instance of this
 * @param {string} name of method
 * @param {object} opts configuration
 * @param {object} contract to match
 * @return {function} for use
 */
const authMethodGenerator = (jsonqlInstance, name, opts, contract) => {
  return (...args) => {
    const params = contract.auth[name].params;
    const values = params.map((p, i) => args[i])
    const header = args[params.length] || {};
    return validateAsync(args, params)
      .then(() => jsonqlInstance
          .query
          .apply(jsonqlInstance, [name, values, header])
      )
      .catch(finalCatch)
  }
}

/**
 * Here just generate the methods calls
 * @param {object} jsonqlInstance what it said
 * @param {object} ee event emitter
 * @param {object} config configuration
 * @param {object} contract the map
 * @return {object} with mapped methods
 */
export default function methodsGenerator(jsonqlInstance, ee, config, contract) {
  let obj = {query: {}, mutation: {}}
  // process the query first
  for (let queryFn in contract.query) {
    // to keep it clean we use a param to id the auth method
    // const fn = (_contract.query[queryFn].auth === true) ? 'auth' : queryFn;
    // generate the query method
    obj.query[queryFn] = (...args) => {
      const params = contract.query[queryFn].params;
      const _args = params.map((param, i) => args[i])
      // debug('query', queryFn, _params);
      // @TODO this need to change
      // the +1 parameter is the extra headers we want to pass
      const header = args[params.length] || {};
      // @TODO validate against the type
      return validateAsync(_args, params)
        .then(() => jsonqlInstance
            .query
            .apply(jsonqlInstance, [queryFn, _args, header])
        )
        .catch(finalCatch)
    }
  }
  // process the mutation, the reason the mutation has a fixed number of parameters
  // there is only the payload, and conditions parameters
  // plus a header at the end
  for (let mutationFn in contract.mutation) {
    obj.mutation[mutationFn] = (payload, conditions, header = {}) => {
      const args = [payload, conditions];
      const params = contract.mutation[mutationFn].params;
      return validateAsync(args, params)
        .then(() => jsonqlInstance
            .mutation
            .apply(jsonqlInstance, [mutationFn, payload, conditions, header])
        )
        .catch(finalCatch)
    }
  }
  // there is only one call issuer we want here
  if (config.enableAuth && contract.auth) {
    obj.auth = {} // v1.3.1 add back the auth prop name
    const { loginHandlerName, logoutHandlerName } = config;
    if (contract.auth[loginHandlerName]) {
      // changing to the name the config specify
      obj.auth[loginHandlerName] = (...args) => {
        const fn = authMethodGenerator(jsonqlInstance, loginHandlerName, config, contract)
        return fn.apply(null, args)
          .then(jsonqlInstance.postLoginAction)
          .then(token => {
            ee.$trigger(ISSUER_NAME, token)
            return token;
          })
      }
    }
    if (contract.auth[logoutHandlerName]) {
      obj.auth[logoutHandlerName] = (...args) => {
        const fn = authMethodGenerator(jsonqlInstance, logoutHandlerName, config, contract)
        return fn.apply(null, args)
          .then(jsonqlInstance.postLogoutAction)
          .then(r => {
            ee.$trigger(LOGOUT_NAME, r)
            return r;
          })
      }
    } else {
      obj.auth[logoutHandlerName] = () => {
        jsonqlInstance.postLogoutAction(KEY_WORD)
        ee.$trigger(LOGOUT_NAME, KEY_WORD)
      }
    }
  }
  return obj;
}
